#include "http_response.h"

const std::unordered_map<int, std::string> HttpResponse::CODE_STATUS_TITLE = {
        { 200, "OK" },
        { 400, "Bad Request" },
        { 403, "Forbidden" },
        { 404, "Not Found" },
};

const std::unordered_map<int, std::string> HttpResponse::ERROR_STATUS_TEXT = {
        { 400, "Your request has bad syntax or is inherently impossible to satisfy." },
        { 403, "You do not have permission to get file form this server." },
        { 404, "The requested file was not found on this server." },
};

const std::unordered_map<std::string, std::string>  HttpResponse::FILETYPE_REF = {
        { ".html",  "text/html" },
        { ".xml",   "text/xml" },
        { ".xhtml", "application/xhtml+xml" },
        { ".txt",   "text/plain" },
        { ".rtf",   "application/rtf" },
        { ".pdf",   "application/pdf" },
        { ".word",  "application/nsword" },
        { ".png",   "image/png" },
        { ".gif",   "image/gif" },
        { ".jpg",   "image/jpeg" },
        { ".jpeg",  "image/jpeg" },
        { ".au",    "audio/basic" },
        { ".mpeg",  "video/mpeg" },
        { ".mpg",   "video/mpeg" },
        { ".avi",   "video/x-msvideo" },
        { ".gz",    "application/x-gzip" },
        { ".tar",   "application/x-tar" },
        { ".css",   "text/css "},
        { ".js",    "text/javascript "},
};




HttpResponse::HttpResponse() {
    m_stat_code = -1;
    m_is_keepalive = false;
    m_file_addr = nullptr;
}

HttpResponse::~HttpResponse() {
    unmap_file();
    delete [] m_write_buffer;
}

void HttpResponse::init_write_buffer(uint64_t write_buf_size) {
    m_write_buf_size = write_buf_size;
    m_write_buffer = new char[write_buf_size]{0};
    m_write_idx = 0;
}

void HttpResponse::reset(){
    m_write_idx = 0;
}

void HttpResponse::init(const string &src_dir, const string &req_path, bool keepalive, int stat_code) {
    unmap_file();
    m_src_dir = src_dir;
    m_req_path = req_path;
    m_is_keepalive = keepalive;
    m_stat_code = stat_code;
    m_write_idx = 0;
}

void HttpResponse::unmap_file() {
    if(m_file_addr != nullptr) {
        munmap(m_file_addr, m_file_stat.st_size);
        m_file_addr = nullptr;
        m_file_stat = {};
    }
}


bool HttpResponse::make_response(struct iovec *iov, int *iov_cnt, uint64_t *bytes_to_send) {
    // 状态码处理
    if (stat((m_src_dir + m_req_path).c_str(), &m_file_stat) < 0 ) {
        // 404 error
        m_stat_code = 404;
        m_req_path = "/" + to_string(m_stat_code) + ".html";
    }
    else if (S_ISDIR(m_file_stat.st_mode)) {
        // 400 error
        m_stat_code = 400;
        m_req_path = "/" + to_string(m_stat_code) + ".html";
    }
    else if (!(m_file_stat.st_mode & S_IROTH)) {
        // 403 error
        m_stat_code = 403;
        m_req_path = "/" + to_string(m_stat_code) + ".html";
    }

    LOG_DEBUG("m_req_path: %s", m_req_path.c_str());
    if (m_stat_code != 200) {
        stat((m_src_dir + m_req_path).c_str(), &m_file_stat);
    }

    add_status_line();
    add_headers();
    add_content();
    process_iov(iov, iov_cnt, bytes_to_send);
}

bool HttpResponse::write_to_buffer(const std::string &msg) {
    int len = snprintf(m_write_buffer + m_write_idx, m_write_buf_size-  m_write_idx - 1,
                       "%s", msg.c_str());
    if (len >= m_write_buf_size - m_write_idx - 1){
        return false;
    }
    m_write_idx += len;
    LOG_INFO("Add to write buffer:\n%s", m_write_buffer);
    return true;
}


bool HttpResponse::add_status_line() {
    std::string status_title = CODE_STATUS_TITLE.find(m_stat_code)->second;
    return write_to_buffer("HTTP/1.1 " + to_string(m_stat_code) + " " + status_title + "\r\n");
}

bool HttpResponse::add_headers() {
    std::string headers;
    if (m_is_keepalive) {
        headers += "Connection: keep-alive\r\n";
        headers += "Keep-Alive: max=6, timeout=120\r\n";
    } else {
        headers += "Connection: close_conn\r\n";
    }
    headers += "Content-Type: " + get_filetype() + "\r\n";
    return write_to_buffer(headers);
}

bool HttpResponse::add_content() {
    // 打开文件
    int src_fd;
    if (m_stat_code != 200) {
        src_fd = open((m_src_dir + m_req_path).c_str(), O_RDONLY);
        LOG_DEBUG("m_req_full_path: %s, %d", (m_src_dir + m_req_path).c_str(), src_fd);
        if (src_fd < 0) {
            // 没有自制的错误页面，返回文字页面
            return add_error_body(ERROR_STATUS_TEXT.find(m_stat_code)->second);
        }

    } else {
        src_fd = open((m_src_dir + m_req_path).c_str(), O_RDONLY);
        if (src_fd < 0) {
            return add_error_body("Failed to open file.");
        }
    }

    // 文件地址映射
    m_file_addr = (char *)mmap(nullptr, m_file_stat.st_size, PROT_READ, MAP_PRIVATE, src_fd, 0);
    close(src_fd);

    if (m_file_addr == MAP_FAILED) {
        return add_error_body("Failed to read file.");
    }

    // 添加长度描述头和空行
    return write_to_buffer("Content-length: " + to_string(m_file_stat.st_size) + "\r\n\r\n");
}

bool HttpResponse::add_error_body(const string &msg) {
    // html文本
    string status_title = CODE_STATUS_TITLE.find(m_stat_code)->second;
    string body;
    body += "<html><title>Error</title>";
    body += "<body bgcolor=\"ffffff\">";
    body += to_string(m_stat_code) + " : " + status_title + "\n";
    body += "<p>" + msg + "</p>";
    body += "<hr><em>MyTinyWebServer</em></body></html>";
    // 添加长度描述头、空行、html文本
    return write_to_buffer("Content-length: " + to_string(body.size()) + "\r\n\r\n" + body);
}

std::string HttpResponse::get_filetype() {
    string::size_type idx = m_req_path.find_last_of('.');
    if(idx == string::npos) {
        // 无类型后缀
        return "text/plain";
    }
    string suffix = m_req_path.substr(idx);
    if (FILETYPE_REF.count(suffix) == 1) {
        return FILETYPE_REF.find(suffix)->second;
    }
    return "text/plain";
}

void HttpResponse::process_iov(struct iovec *iov, int *iov_cnt, uint64_t *bytes_to_send) {
    // 响应头
    iov[0].iov_base = m_write_buffer;
    iov[0].iov_len = m_write_idx;
    *iov_cnt = 1;
    *bytes_to_send = iov[0].iov_len;
    // 文件
    if (m_file_stat.st_size != 0 && m_file_addr != nullptr) {
        iov[1].iov_base = m_file_addr;
        iov[1].iov_len = m_file_stat.st_size;
        *iov_cnt = 2;
        *bytes_to_send = iov[0].iov_len + iov[1].iov_len;
    }
}













